package pgx_test

import (
	"bytes"
	"context"
	"net"
	"os"
	"reflect"
	"strings"
	"testing"
	"time"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxtest"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestDateTranscode(t *testing.T) {
	t.Parallel()

	pgxtest.RunWithQueryExecModes(context.Background(), t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
		dates := []time.Time{
			time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC),
			time.Date(1000, 1, 1, 0, 0, 0, 0, time.UTC),
			time.Date(1600, 1, 1, 0, 0, 0, 0, time.UTC),
			time.Date(1700, 1, 1, 0, 0, 0, 0, time.UTC),
			time.Date(1800, 1, 1, 0, 0, 0, 0, time.UTC),
			time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC),
			time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC),
			time.Date(1999, 12, 31, 0, 0, 0, 0, time.UTC),
			time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
			time.Date(2001, 1, 2, 0, 0, 0, 0, time.UTC),
			time.Date(2004, 2, 29, 0, 0, 0, 0, time.UTC),
			time.Date(2013, 7, 4, 0, 0, 0, 0, time.UTC),
			time.Date(2013, 12, 25, 0, 0, 0, 0, time.UTC),
			time.Date(2029, 1, 1, 0, 0, 0, 0, time.UTC),
			time.Date(2081, 1, 1, 0, 0, 0, 0, time.UTC),
			time.Date(2096, 2, 29, 0, 0, 0, 0, time.UTC),
			time.Date(2550, 1, 1, 0, 0, 0, 0, time.UTC),
			time.Date(9999, 12, 31, 0, 0, 0, 0, time.UTC),
		}

		for _, actualDate := range dates {
			var d time.Time

			err := conn.QueryRow(context.Background(), "select $1::date", actualDate).Scan(&d)
			if err != nil {
				t.Fatalf("Unexpected failure on QueryRow Scan: %v", err)
			}
			if !actualDate.Equal(d) {
				t.Errorf("Did not transcode date successfully: %v is not %v", d, actualDate)
			}
		}
	})
}

func TestTimestampTzTranscode(t *testing.T) {
	t.Parallel()

	pgxtest.RunWithQueryExecModes(context.Background(), t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
		inputTime := time.Date(2013, 1, 2, 3, 4, 5, 6000, time.Local)

		var outputTime time.Time

		err := conn.QueryRow(context.Background(), "select $1::timestamptz", inputTime).Scan(&outputTime)
		if err != nil {
			t.Fatalf("QueryRow Scan failed: %v", err)
		}
		if !inputTime.Equal(outputTime) {
			t.Errorf("Did not transcode time successfully: %v is not %v", outputTime, inputTime)
		}
	})
}

// TODO - move these tests to pgtype

func TestJSONAndJSONBTranscode(t *testing.T) {
	t.Parallel()

	pgxtest.RunWithQueryExecModes(context.Background(), t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
		for _, typename := range []string{"json", "jsonb"} {
			if _, ok := conn.TypeMap().TypeForName(typename); !ok {
				continue // No JSON/JSONB type -- must be running against old PostgreSQL
			}

			testJSONString(t, conn, typename)
			testJSONStringPointer(t, conn, typename)
		}
	})
}

func TestJSONAndJSONBTranscodeExtendedOnly(t *testing.T) {
	t.Parallel()

	conn := mustConnectString(t, os.Getenv("PGX_TEST_DATABASE"))
	defer closeConn(t, conn)

	for _, typename := range []string{"json", "jsonb"} {
		if _, ok := conn.TypeMap().TypeForName(typename); !ok {
			continue // No JSON/JSONB type -- must be running against old PostgreSQL
		}
		testJSONSingleLevelStringMap(t, conn, typename)
		testJSONNestedMap(t, conn, typename)
		testJSONStringArray(t, conn, typename)
		testJSONInt64Array(t, conn, typename)
		testJSONInt16ArrayFailureDueToOverflow(t, conn, typename)
		testJSONStruct(t, conn, typename)
	}

}

func testJSONString(t testing.TB, conn *pgx.Conn, typename string) {
	input := `{"key": "value"}`
	expectedOutput := map[string]string{"key": "value"}
	var output map[string]string
	err := conn.QueryRow(context.Background(), "select $1::"+typename, input).Scan(&output)
	if err != nil {
		t.Errorf("%s: QueryRow Scan failed: %v", typename, err)
		return
	}

	if !reflect.DeepEqual(expectedOutput, output) {
		t.Errorf("%s: Did not transcode map[string]string successfully: %v is not %v", typename, expectedOutput, output)
		return
	}
}

func testJSONStringPointer(t testing.TB, conn *pgx.Conn, typename string) {
	input := `{"key": "value"}`
	expectedOutput := map[string]string{"key": "value"}
	var output map[string]string
	err := conn.QueryRow(context.Background(), "select $1::"+typename, &input).Scan(&output)
	if err != nil {
		t.Errorf("%s: QueryRow Scan failed: %v", typename, err)
		return
	}

	if !reflect.DeepEqual(expectedOutput, output) {
		t.Errorf("%s: Did not transcode map[string]string successfully: %v is not %v", typename, expectedOutput, output)
		return
	}
}

func testJSONSingleLevelStringMap(t *testing.T, conn *pgx.Conn, typename string) {
	input := map[string]string{"key": "value"}
	var output map[string]string
	err := conn.QueryRow(context.Background(), "select $1::"+typename, input).Scan(&output)
	if err != nil {
		t.Errorf("%s: QueryRow Scan failed: %v", typename, err)
		return
	}

	if !reflect.DeepEqual(input, output) {
		t.Errorf("%s: Did not transcode map[string]string successfully: %v is not %v", typename, input, output)
		return
	}
}

func testJSONNestedMap(t *testing.T, conn *pgx.Conn, typename string) {
	input := map[string]any{
		"name":      "Uncanny",
		"stats":     map[string]any{"hp": float64(107), "maxhp": float64(150)},
		"inventory": []any{"phone", "key"},
	}
	var output map[string]any
	err := conn.QueryRow(context.Background(), "select $1::"+typename, input).Scan(&output)
	if err != nil {
		t.Errorf("%s: QueryRow Scan failed: %v", typename, err)
		return
	}

	if !reflect.DeepEqual(input, output) {
		t.Errorf("%s: Did not transcode map[string]any successfully: %v is not %v", typename, input, output)
		return
	}
}

func testJSONStringArray(t *testing.T, conn *pgx.Conn, typename string) {
	input := []string{"foo", "bar", "baz"}
	var output []string
	err := conn.QueryRow(context.Background(), "select $1::"+typename, input).Scan(&output)
	if err != nil {
		t.Errorf("%s: QueryRow Scan failed: %v", typename, err)
	}

	if !reflect.DeepEqual(input, output) {
		t.Errorf("%s: Did not transcode []string successfully: %v is not %v", typename, input, output)
	}
}

func testJSONInt64Array(t *testing.T, conn *pgx.Conn, typename string) {
	input := []int64{1, 2, 234432}
	var output []int64
	err := conn.QueryRow(context.Background(), "select $1::"+typename, input).Scan(&output)
	if err != nil {
		t.Errorf("%s: QueryRow Scan failed: %v", typename, err)
	}

	if !reflect.DeepEqual(input, output) {
		t.Errorf("%s: Did not transcode []int64 successfully: %v is not %v", typename, input, output)
	}
}

func testJSONInt16ArrayFailureDueToOverflow(t *testing.T, conn *pgx.Conn, typename string) {
	input := []int{1, 2, 234432}
	var output []int16
	err := conn.QueryRow(context.Background(), "select $1::"+typename, input).Scan(&output)
	if err == nil || err.Error() != "can't scan into dest[0]: json: cannot unmarshal number 234432 into Go value of type int16" {
		t.Errorf("%s: Expected *json.UnmarkalTypeError, but got %v", typename, err)
	}
}

func testJSONStruct(t *testing.T, conn *pgx.Conn, typename string) {
	type person struct {
		Name string `json:"name"`
		Age  int    `json:"age"`
	}

	input := person{
		Name: "John",
		Age:  42,
	}

	var output person

	err := conn.QueryRow(context.Background(), "select $1::"+typename, input).Scan(&output)
	if err != nil {
		t.Errorf("%s: QueryRow Scan failed: %v", typename, err)
	}

	if !reflect.DeepEqual(input, output) {
		t.Errorf("%s: Did not transcode struct successfully: %v is not %v", typename, input, output)
	}
}

func mustParseCIDR(t testing.TB, s string) *net.IPNet {
	_, ipnet, err := net.ParseCIDR(s)
	if err != nil {
		t.Fatal(err)
	}

	return ipnet
}

func TestInetCIDRTranscodeIPNet(t *testing.T) {
	t.Parallel()

	pgxtest.RunWithQueryExecModes(context.Background(), t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
		tests := []struct {
			sql   string
			value *net.IPNet
		}{
			{"select $1::inet", mustParseCIDR(t, "0.0.0.0/32")},
			{"select $1::inet", mustParseCIDR(t, "127.0.0.1/32")},
			{"select $1::inet", mustParseCIDR(t, "12.34.56.0/32")},
			{"select $1::inet", mustParseCIDR(t, "192.168.1.0/24")},
			{"select $1::inet", mustParseCIDR(t, "255.0.0.0/8")},
			{"select $1::inet", mustParseCIDR(t, "255.255.255.255/32")},
			{"select $1::inet", mustParseCIDR(t, "::/128")},
			{"select $1::inet", mustParseCIDR(t, "::/0")},
			{"select $1::inet", mustParseCIDR(t, "::1/128")},
			{"select $1::inet", mustParseCIDR(t, "2607:f8b0:4009:80b::200e/128")},
			{"select $1::cidr", mustParseCIDR(t, "0.0.0.0/32")},
			{"select $1::cidr", mustParseCIDR(t, "127.0.0.1/32")},
			{"select $1::cidr", mustParseCIDR(t, "12.34.56.0/32")},
			{"select $1::cidr", mustParseCIDR(t, "192.168.1.0/24")},
			{"select $1::cidr", mustParseCIDR(t, "255.0.0.0/8")},
			{"select $1::cidr", mustParseCIDR(t, "255.255.255.255/32")},
			{"select $1::cidr", mustParseCIDR(t, "::/128")},
			{"select $1::cidr", mustParseCIDR(t, "::/0")},
			{"select $1::cidr", mustParseCIDR(t, "::1/128")},
			{"select $1::cidr", mustParseCIDR(t, "2607:f8b0:4009:80b::200e/128")},
		}

		for i, tt := range tests {
			if conn.PgConn().ParameterStatus("crdb_version") != "" && strings.Contains(tt.sql, "cidr") {
				t.Log("Server does not support cidr type (https://github.com/cockroachdb/cockroach/issues/18846)")
				continue
			}

			var actual net.IPNet

			err := conn.QueryRow(context.Background(), tt.sql, tt.value).Scan(&actual)
			if err != nil {
				t.Errorf("%d. Unexpected failure: %v (sql -> %v, value -> %v)", i, err, tt.sql, tt.value)
				continue
			}

			if actual.String() != tt.value.String() {
				t.Errorf("%d. Expected %v, got %v (sql -> %v)", i, tt.value, actual, tt.sql)
			}
		}
	})
}

func TestInetCIDRTranscodeIP(t *testing.T) {
	t.Parallel()

	pgxtest.RunWithQueryExecModes(context.Background(), t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
		tests := []struct {
			sql   string
			value net.IP
		}{
			{"select $1::inet", net.ParseIP("0.0.0.0")},
			{"select $1::inet", net.ParseIP("127.0.0.1")},
			{"select $1::inet", net.ParseIP("12.34.56.0")},
			{"select $1::inet", net.ParseIP("255.255.255.255")},
			{"select $1::inet", net.ParseIP("::1")},
			{"select $1::inet", net.ParseIP("2607:f8b0:4009:80b::200e")},
			{"select $1::cidr", net.ParseIP("0.0.0.0")},
			{"select $1::cidr", net.ParseIP("127.0.0.1")},
			{"select $1::cidr", net.ParseIP("12.34.56.0")},
			{"select $1::cidr", net.ParseIP("255.255.255.255")},
			{"select $1::cidr", net.ParseIP("::1")},
			{"select $1::cidr", net.ParseIP("2607:f8b0:4009:80b::200e")},
		}

		for i, tt := range tests {
			if conn.PgConn().ParameterStatus("crdb_version") != "" && strings.Contains(tt.sql, "cidr") {
				t.Log("Server does not support cidr type (https://github.com/cockroachdb/cockroach/issues/18846)")
				continue
			}

			var actual net.IP

			err := conn.QueryRow(context.Background(), tt.sql, tt.value).Scan(&actual)
			if err != nil {
				t.Errorf("%d. Unexpected failure: %v (sql -> %v, value -> %v)", i, err, tt.sql, tt.value)
				continue
			}

			if !actual.Equal(tt.value) {
				t.Errorf("%d. Expected %v, got %v (sql -> %v)", i, tt.value, actual, tt.sql)
			}

			ensureConnValid(t, conn)
		}

		failTests := []struct {
			sql   string
			value *net.IPNet
		}{
			{"select $1::inet", mustParseCIDR(t, "192.168.1.0/24")},
			{"select $1::cidr", mustParseCIDR(t, "192.168.1.0/24")},
		}
		for i, tt := range failTests {
			var actual net.IP

			err := conn.QueryRow(context.Background(), tt.sql, tt.value).Scan(&actual)
			if err == nil {
				t.Errorf("%d. Expected failure but got none", i)
				continue
			}

			ensureConnValid(t, conn)
		}
	})
}

func TestInetCIDRArrayTranscodeIPNet(t *testing.T) {
	t.Parallel()

	pgxtest.RunWithQueryExecModes(context.Background(), t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
		tests := []struct {
			sql   string
			value []*net.IPNet
		}{
			{
				"select $1::inet[]",
				[]*net.IPNet{
					mustParseCIDR(t, "0.0.0.0/32"),
					mustParseCIDR(t, "127.0.0.1/32"),
					mustParseCIDR(t, "12.34.56.0/32"),
					mustParseCIDR(t, "192.168.1.0/24"),
					mustParseCIDR(t, "255.0.0.0/8"),
					mustParseCIDR(t, "255.255.255.255/32"),
					mustParseCIDR(t, "::/128"),
					mustParseCIDR(t, "::/0"),
					mustParseCIDR(t, "::1/128"),
					mustParseCIDR(t, "2607:f8b0:4009:80b::200e/128"),
				},
			},
			{
				"select $1::cidr[]",
				[]*net.IPNet{
					mustParseCIDR(t, "0.0.0.0/32"),
					mustParseCIDR(t, "127.0.0.1/32"),
					mustParseCIDR(t, "12.34.56.0/32"),
					mustParseCIDR(t, "192.168.1.0/24"),
					mustParseCIDR(t, "255.0.0.0/8"),
					mustParseCIDR(t, "255.255.255.255/32"),
					mustParseCIDR(t, "::/128"),
					mustParseCIDR(t, "::/0"),
					mustParseCIDR(t, "::1/128"),
					mustParseCIDR(t, "2607:f8b0:4009:80b::200e/128"),
				},
			},
		}

		for i, tt := range tests {
			if conn.PgConn().ParameterStatus("crdb_version") != "" && strings.Contains(tt.sql, "cidr") {
				t.Log("Server does not support cidr type (https://github.com/cockroachdb/cockroach/issues/18846)")
				continue
			}

			var actual []*net.IPNet

			err := conn.QueryRow(context.Background(), tt.sql, tt.value).Scan(&actual)
			if err != nil {
				t.Errorf("%d. Unexpected failure: %v (sql -> %v, value -> %v)", i, err, tt.sql, tt.value)
				continue
			}

			if !reflect.DeepEqual(actual, tt.value) {
				t.Errorf("%d. Expected %v, got %v (sql -> %v)", i, tt.value, actual, tt.sql)
			}

			ensureConnValid(t, conn)
		}
	})
}

func TestInetCIDRArrayTranscodeIP(t *testing.T) {
	t.Parallel()

	pgxtest.RunWithQueryExecModes(context.Background(), t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
		tests := []struct {
			sql   string
			value []net.IP
		}{
			{
				"select $1::inet[]",
				[]net.IP{
					net.ParseIP("0.0.0.0"),
					net.ParseIP("127.0.0.1"),
					net.ParseIP("12.34.56.0"),
					net.ParseIP("255.255.255.255"),
					net.ParseIP("2607:f8b0:4009:80b::200e"),
				},
			},
			{
				"select $1::cidr[]",
				[]net.IP{
					net.ParseIP("0.0.0.0"),
					net.ParseIP("127.0.0.1"),
					net.ParseIP("12.34.56.0"),
					net.ParseIP("255.255.255.255"),
					net.ParseIP("2607:f8b0:4009:80b::200e"),
				},
			},
		}

		for i, tt := range tests {
			if conn.PgConn().ParameterStatus("crdb_version") != "" && strings.Contains(tt.sql, "cidr") {
				t.Log("Server does not support cidr type (https://github.com/cockroachdb/cockroach/issues/18846)")
				continue
			}

			var actual []net.IP

			err := conn.QueryRow(context.Background(), tt.sql, tt.value).Scan(&actual)
			if err != nil {
				t.Errorf("%d. Unexpected failure: %v (sql -> %v, value -> %v)", i, err, tt.sql, tt.value)
				continue
			}

			assert.Equal(t, len(tt.value), len(actual), "%d", i)
			for j := range actual {
				assert.True(t, actual[j].Equal(tt.value[j]), "%d", i)
			}

			ensureConnValid(t, conn)
		}

		failTests := []struct {
			sql   string
			value []*net.IPNet
		}{
			{
				"select $1::inet[]",
				[]*net.IPNet{
					mustParseCIDR(t, "12.34.56.0/32"),
					mustParseCIDR(t, "192.168.1.0/24"),
				},
			},
			{
				"select $1::cidr[]",
				[]*net.IPNet{
					mustParseCIDR(t, "12.34.56.0/32"),
					mustParseCIDR(t, "192.168.1.0/24"),
				},
			},
		}

		for i, tt := range failTests {
			var actual []net.IP

			err := conn.QueryRow(context.Background(), tt.sql, tt.value).Scan(&actual)
			if err == nil {
				t.Errorf("%d. Expected failure but got none", i)
				continue
			}

			ensureConnValid(t, conn)
		}
	})
}

func TestInetCIDRTranscodeWithJustIP(t *testing.T) {
	t.Parallel()

	pgxtest.RunWithQueryExecModes(context.Background(), t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
		tests := []struct {
			sql   string
			value string
		}{
			{"select $1::inet", "0.0.0.0/32"},
			{"select $1::inet", "127.0.0.1/32"},
			{"select $1::inet", "12.34.56.0/32"},
			{"select $1::inet", "255.255.255.255/32"},
			{"select $1::inet", "::/128"},
			{"select $1::inet", "2607:f8b0:4009:80b::200e/128"},
			{"select $1::cidr", "0.0.0.0/32"},
			{"select $1::cidr", "127.0.0.1/32"},
			{"select $1::cidr", "12.34.56.0/32"},
			{"select $1::cidr", "255.255.255.255/32"},
			{"select $1::cidr", "::/128"},
			{"select $1::cidr", "2607:f8b0:4009:80b::200e/128"},
		}

		for i, tt := range tests {
			if conn.PgConn().ParameterStatus("crdb_version") != "" && strings.Contains(tt.sql, "cidr") {
				t.Log("Server does not support cidr type (https://github.com/cockroachdb/cockroach/issues/18846)")
				continue
			}

			expected := mustParseCIDR(t, tt.value)
			var actual net.IPNet

			err := conn.QueryRow(context.Background(), tt.sql, expected.IP).Scan(&actual)
			if err != nil {
				t.Errorf("%d. Unexpected failure: %v (sql -> %v, value -> %v)", i, err, tt.sql, tt.value)
				continue
			}

			if actual.String() != expected.String() {
				t.Errorf("%d. Expected %v, got %v (sql -> %v)", i, tt.value, actual, tt.sql)
			}

			ensureConnValid(t, conn)
		}
	})
}

func TestArrayDecoding(t *testing.T) {
	t.Parallel()

	pgxtest.RunWithQueryExecModes(context.Background(), t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
		tests := []struct {
			sql    string
			query  any
			scan   any
			assert func(testing.TB, any, any)
		}{
			{
				"select $1::bool[]", []bool{true, false, true}, &[]bool{},
				func(t testing.TB, query, scan any) {
					if !reflect.DeepEqual(query, *(scan.(*[]bool))) {
						t.Errorf("failed to encode bool[]")
					}
				},
			},
			{
				"select $1::smallint[]", []int16{2, 4, 484, 32767}, &[]int16{},
				func(t testing.TB, query, scan any) {
					if !reflect.DeepEqual(query, *(scan.(*[]int16))) {
						t.Errorf("failed to encode smallint[]")
					}
				},
			},
			{
				"select $1::smallint[]", []uint16{2, 4, 484, 32767}, &[]uint16{},
				func(t testing.TB, query, scan any) {
					if !reflect.DeepEqual(query, *(scan.(*[]uint16))) {
						t.Errorf("failed to encode smallint[]")
					}
				},
			},
			{
				"select $1::int[]", []int32{2, 4, 484}, &[]int32{},
				func(t testing.TB, query, scan any) {
					if !reflect.DeepEqual(query, *(scan.(*[]int32))) {
						t.Errorf("failed to encode int[]")
					}
				},
			},
			{
				"select $1::int[]", []uint32{2, 4, 484, 2147483647}, &[]uint32{},
				func(t testing.TB, query, scan any) {
					if !reflect.DeepEqual(query, *(scan.(*[]uint32))) {
						t.Errorf("failed to encode int[]")
					}
				},
			},
			{
				"select $1::bigint[]", []int64{2, 4, 484, 9223372036854775807}, &[]int64{},
				func(t testing.TB, query, scan any) {
					if !reflect.DeepEqual(query, *(scan.(*[]int64))) {
						t.Errorf("failed to encode bigint[]")
					}
				},
			},
			{
				"select $1::bigint[]", []uint64{2, 4, 484, 9223372036854775807}, &[]uint64{},
				func(t testing.TB, query, scan any) {
					if !reflect.DeepEqual(query, *(scan.(*[]uint64))) {
						t.Errorf("failed to encode bigint[]")
					}
				},
			},
			{
				"select $1::text[]", []string{"it's", "over", "9000!"}, &[]string{},
				func(t testing.TB, query, scan any) {
					if !reflect.DeepEqual(query, *(scan.(*[]string))) {
						t.Errorf("failed to encode text[]")
					}
				},
			},
			{
				"select $1::timestamptz[]", []time.Time{time.Unix(323232, 0), time.Unix(3239949334, 00)}, &[]time.Time{},
				func(t testing.TB, query, scan any) {
					queryTimeSlice := query.([]time.Time)
					scanTimeSlice := *(scan.(*[]time.Time))
					require.Equal(t, len(queryTimeSlice), len(scanTimeSlice))
					for i := range queryTimeSlice {
						assert.Truef(t, queryTimeSlice[i].Equal(scanTimeSlice[i]), "%d", i)
					}
				},
			},
			{
				"select $1::bytea[]", [][]byte{{0, 1, 2, 3}, {4, 5, 6, 7}}, &[][]byte{},
				func(t testing.TB, query, scan any) {
					queryBytesSliceSlice := query.([][]byte)
					scanBytesSliceSlice := *(scan.(*[][]byte))
					if len(queryBytesSliceSlice) != len(scanBytesSliceSlice) {
						t.Errorf("failed to encode byte[][] to bytea[]: expected %d to equal %d", len(queryBytesSliceSlice), len(scanBytesSliceSlice))
					}
					for i := range queryBytesSliceSlice {
						qb := queryBytesSliceSlice[i]
						sb := scanBytesSliceSlice[i]
						if !bytes.Equal(qb, sb) {
							t.Errorf("failed to encode byte[][] to bytea[]: expected %v to equal %v", qb, sb)
						}
					}
				},
			},
		}

		for i, tt := range tests {
			err := conn.QueryRow(context.Background(), tt.sql, tt.query).Scan(tt.scan)
			if err != nil {
				t.Errorf(`%d. error reading array: %v`, i, err)
				continue
			}
			tt.assert(t, tt.query, tt.scan)
			ensureConnValid(t, conn)
		}
	})
}

func TestEmptyArrayDecoding(t *testing.T) {
	t.Parallel()

	pgxtest.RunWithQueryExecModes(context.Background(), t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
		var val []string

		err := conn.QueryRow(context.Background(), "select array[]::text[]").Scan(&val)
		if err != nil {
			t.Errorf(`error reading array: %v`, err)
		}
		if len(val) != 0 {
			t.Errorf("Expected 0 values, got %d", len(val))
		}

		var n, m int32

		err = conn.QueryRow(context.Background(), "select 1::integer, array[]::text[], 42::integer").Scan(&n, &val, &m)
		if err != nil {
			t.Errorf(`error reading array: %v`, err)
		}
		if len(val) != 0 {
			t.Errorf("Expected 0 values, got %d", len(val))
		}
		if n != 1 {
			t.Errorf("Expected n to be 1, but it was %d", n)
		}
		if m != 42 {
			t.Errorf("Expected n to be 42, but it was %d", n)
		}

		rows, err := conn.Query(context.Background(), "select 1::integer, array['test']::text[] union select 2::integer, array[]::text[] union select 3::integer, array['test']::text[]")
		if err != nil {
			t.Errorf(`error retrieving rows with array: %v`, err)
		}
		defer rows.Close()

		for rows.Next() {
			err = rows.Scan(&n, &val)
			if err != nil {
				t.Errorf(`error reading array: %v`, err)
			}
		}
	})
}

func TestPointerPointer(t *testing.T) {
	t.Parallel()

	pgxtest.RunWithQueryExecModes(context.Background(), t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
		pgxtest.SkipCockroachDB(t, conn, "Server auto converts ints to bigint and test relies on exact types")

		type allTypes struct {
			s   *string
			i16 *int16
			i32 *int32
			i64 *int64
			f32 *float32
			f64 *float64
			b   *bool
			t   *time.Time
		}

		var actual, zero, expected allTypes

		{
			s := "foo"
			expected.s = &s
			i16 := int16(1)
			expected.i16 = &i16
			i32 := int32(1)
			expected.i32 = &i32
			i64 := int64(1)
			expected.i64 = &i64
			f32 := float32(1.23)
			expected.f32 = &f32
			f64 := float64(1.23)
			expected.f64 = &f64
			b := true
			expected.b = &b
			t := time.Unix(123, 5000)
			expected.t = &t
		}

		tests := []struct {
			sql       string
			queryArgs []any
			scanArgs  []any
			expected  allTypes
		}{
			{"select $1::text", []any{expected.s}, []any{&actual.s}, allTypes{s: expected.s}},
			{"select $1::text", []any{zero.s}, []any{&actual.s}, allTypes{}},
			{"select $1::int2", []any{expected.i16}, []any{&actual.i16}, allTypes{i16: expected.i16}},
			{"select $1::int2", []any{zero.i16}, []any{&actual.i16}, allTypes{}},
			{"select $1::int4", []any{expected.i32}, []any{&actual.i32}, allTypes{i32: expected.i32}},
			{"select $1::int4", []any{zero.i32}, []any{&actual.i32}, allTypes{}},
			{"select $1::int8", []any{expected.i64}, []any{&actual.i64}, allTypes{i64: expected.i64}},
			{"select $1::int8", []any{zero.i64}, []any{&actual.i64}, allTypes{}},
			{"select $1::float4", []any{expected.f32}, []any{&actual.f32}, allTypes{f32: expected.f32}},
			{"select $1::float4", []any{zero.f32}, []any{&actual.f32}, allTypes{}},
			{"select $1::float8", []any{expected.f64}, []any{&actual.f64}, allTypes{f64: expected.f64}},
			{"select $1::float8", []any{zero.f64}, []any{&actual.f64}, allTypes{}},
			{"select $1::bool", []any{expected.b}, []any{&actual.b}, allTypes{b: expected.b}},
			{"select $1::bool", []any{zero.b}, []any{&actual.b}, allTypes{}},
			{"select $1::timestamptz", []any{expected.t}, []any{&actual.t}, allTypes{t: expected.t}},
			{"select $1::timestamptz", []any{zero.t}, []any{&actual.t}, allTypes{}},
		}

		for i, tt := range tests {
			actual = zero

			err := conn.QueryRow(context.Background(), tt.sql, tt.queryArgs...).Scan(tt.scanArgs...)
			if err != nil {
				t.Errorf("%d. Unexpected failure: %v (sql -> %v, queryArgs -> %v)", i, err, tt.sql, tt.queryArgs)
			}

			assert.Equal(t, tt.expected.s, actual.s)
			assert.Equal(t, tt.expected.i16, actual.i16)
			assert.Equal(t, tt.expected.i32, actual.i32)
			assert.Equal(t, tt.expected.i64, actual.i64)
			assert.Equal(t, tt.expected.f32, actual.f32)
			assert.Equal(t, tt.expected.f64, actual.f64)
			assert.Equal(t, tt.expected.b, actual.b)
			if tt.expected.t != nil || actual.t != nil {
				assert.True(t, tt.expected.t.Equal(*actual.t))
			}

			ensureConnValid(t, conn)
		}
	})
}

func TestPointerPointerNonZero(t *testing.T) {
	t.Parallel()

	pgxtest.RunWithQueryExecModes(context.Background(), t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
		f := "foo"
		dest := &f

		err := conn.QueryRow(context.Background(), "select $1::text", nil).Scan(&dest)
		if err != nil {
			t.Errorf("Unexpected failure scanning: %v", err)
		}
		if dest != nil {
			t.Errorf("Expected dest to be nil, got %#v", dest)
		}
	})
}

func TestEncodeTypeRename(t *testing.T) {
	t.Parallel()

	pgxtest.RunWithQueryExecModes(context.Background(), t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
		type _int int
		inInt := _int(1)
		var outInt _int

		type _int8 int8
		inInt8 := _int8(2)
		var outInt8 _int8

		type _int16 int16
		inInt16 := _int16(3)
		var outInt16 _int16

		type _int32 int32
		inInt32 := _int32(4)
		var outInt32 _int32

		type _int64 int64
		inInt64 := _int64(5)
		var outInt64 _int64

		type _uint uint
		inUint := _uint(6)
		var outUint _uint

		type _uint8 uint8
		inUint8 := _uint8(7)
		var outUint8 _uint8

		type _uint16 uint16
		inUint16 := _uint16(8)
		var outUint16 _uint16

		type _uint32 uint32
		inUint32 := _uint32(9)
		var outUint32 _uint32

		type _uint64 uint64
		inUint64 := _uint64(10)
		var outUint64 _uint64

		type _string string
		inString := _string("foo")
		var outString _string

		// pgx.QueryExecModeExec requires all types to be registered.
		conn.TypeMap().RegisterDefaultPgType(inInt, "int8")
		conn.TypeMap().RegisterDefaultPgType(inInt8, "int8")
		conn.TypeMap().RegisterDefaultPgType(inInt16, "int8")
		conn.TypeMap().RegisterDefaultPgType(inInt32, "int8")
		conn.TypeMap().RegisterDefaultPgType(inInt64, "int8")
		conn.TypeMap().RegisterDefaultPgType(inUint, "int8")
		conn.TypeMap().RegisterDefaultPgType(inUint8, "int8")
		conn.TypeMap().RegisterDefaultPgType(inUint16, "int8")
		conn.TypeMap().RegisterDefaultPgType(inUint32, "int8")
		conn.TypeMap().RegisterDefaultPgType(inUint64, "int8")
		conn.TypeMap().RegisterDefaultPgType(inString, "text")

		err := conn.QueryRow(context.Background(), "select $1::int, $2::int, $3::int2, $4::int4, $5::int8, $6::int, $7::int, $8::int, $9::int, $10::int, $11::text",
			inInt, inInt8, inInt16, inInt32, inInt64, inUint, inUint8, inUint16, inUint32, inUint64, inString,
		).Scan(&outInt, &outInt8, &outInt16, &outInt32, &outInt64, &outUint, &outUint8, &outUint16, &outUint32, &outUint64, &outString)
		if err != nil {
			t.Fatalf("Failed with type rename: %v", err)
		}

		if inInt != outInt {
			t.Errorf("int rename: expected %v, got %v", inInt, outInt)
		}

		if inInt8 != outInt8 {
			t.Errorf("int8 rename: expected %v, got %v", inInt8, outInt8)
		}

		if inInt16 != outInt16 {
			t.Errorf("int16 rename: expected %v, got %v", inInt16, outInt16)
		}

		if inInt32 != outInt32 {
			t.Errorf("int32 rename: expected %v, got %v", inInt32, outInt32)
		}

		if inInt64 != outInt64 {
			t.Errorf("int64 rename: expected %v, got %v", inInt64, outInt64)
		}

		if inUint != outUint {
			t.Errorf("uint rename: expected %v, got %v", inUint, outUint)
		}

		if inUint8 != outUint8 {
			t.Errorf("uint8 rename: expected %v, got %v", inUint8, outUint8)
		}

		if inUint16 != outUint16 {
			t.Errorf("uint16 rename: expected %v, got %v", inUint16, outUint16)
		}

		if inUint32 != outUint32 {
			t.Errorf("uint32 rename: expected %v, got %v", inUint32, outUint32)
		}

		if inUint64 != outUint64 {
			t.Errorf("uint64 rename: expected %v, got %v", inUint64, outUint64)
		}

		if inString != outString {
			t.Errorf("string rename: expected %v, got %v", inString, outString)
		}
	})
}

// func TestRowDecodeBinary(t *testing.T) {
// 	t.Parallel()

// 	conn := mustConnectString(t, os.Getenv("PGX_TEST_DATABASE"))
// 	defer closeConn(t, conn)

// 	tests := []struct {
// 		sql      string
// 		expected []any
// 	}{
// 		{
// 			"select row(1, 'cat', '2015-01-01 08:12:42-00'::timestamptz)",
// 			[]any{
// 				int32(1),
// 				"cat",
// 				time.Date(2015, 1, 1, 8, 12, 42, 0, time.UTC).Local(),
// 			},
// 		},
// 		{
// 			"select row(100.0::float, 1.09::float)",
// 			[]any{
// 				float64(100),
// 				float64(1.09),
// 			},
// 		},
// 	}

// 	for i, tt := range tests {
// 		var actual []any

// 		err := conn.QueryRow(context.Background(), tt.sql).Scan(&actual)
// 		if err != nil {
// 			t.Errorf("%d. Unexpected failure: %v (sql -> %v)", i, err, tt.sql)
// 			continue
// 		}

// 		for j := range tt.expected {
// 			assert.EqualValuesf(t, tt.expected[j], actual[j], "%d. [%d]", i, j)

// 		}

// 		ensureConnValid(t, conn)
// 	}
// }

// https://github.com/jackc/pgx/issues/810
func TestRowsScanNilThenScanValue(t *testing.T) {
	t.Parallel()

	pgxtest.RunWithQueryExecModes(context.Background(), t, defaultConnTestRunner, nil, func(ctx context.Context, t testing.TB, conn *pgx.Conn) {
		sql := `select null as a, null as b
union
select 1, 2
order by a nulls first
`
		rows, err := conn.Query(context.Background(), sql)
		require.NoError(t, err)

		require.True(t, rows.Next())

		err = rows.Scan(nil, nil)
		require.NoError(t, err)

		require.True(t, rows.Next())

		var a int
		var b int
		err = rows.Scan(&a, &b)
		require.NoError(t, err)

		require.EqualValues(t, 1, a)
		require.EqualValues(t, 2, b)

		rows.Close()
		require.NoError(t, rows.Err())
	})
}

func TestScanIntoByteSlice(t *testing.T) {
	t.Parallel()

	conn := mustConnectString(t, os.Getenv("PGX_TEST_DATABASE"))
	defer closeConn(t, conn)
	// Success cases
	for _, tt := range []struct {
		name             string
		sql              string
		resultFormatCode int16
		output           []byte
	}{
		{"int - text", "select 42", pgx.TextFormatCode, []byte("42")},
		{"int - binary", "select 42", pgx.BinaryFormatCode, []byte("42")},
		{"text - text", "select 'hi'", pgx.TextFormatCode, []byte("hi")},
		{"text - binary", "select 'hi'", pgx.BinaryFormatCode, []byte("hi")},
		{"json - text", "select '{}'::json", pgx.TextFormatCode, []byte("{}")},
		{"json - binary", "select '{}'::json", pgx.BinaryFormatCode, []byte("{}")},
		{"jsonb - text", "select '{}'::jsonb", pgx.TextFormatCode, []byte("{}")},
		{"jsonb - binary", "select '{}'::jsonb", pgx.BinaryFormatCode, []byte("{}")},
	} {
		t.Run(tt.name, func(t *testing.T) {
			var buf []byte
			err := conn.QueryRow(context.Background(), tt.sql, pgx.QueryResultFormats{tt.resultFormatCode}).Scan(&buf)
			require.NoError(t, err)
			require.Equal(t, tt.output, buf)
		})
	}
}
